/*
 * Copyright 2002-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.context.i18n;

import java.util.Locale;
import java.util.TimeZone;

import org.springframework.core.NamedInheritableThreadLocal;
import org.springframework.core.NamedThreadLocal;
import org.springframework.lang.Nullable;

/**
 * 将LocaleContext实例与当前线程关联的简单holder类. 如果{@code inheritable}
 * 标志设置为{@code true}, 则LocaleContext将由当前线程派生的任何子线程继承.
 *
 * <p>在任何必要的地方用作Spring中当前Locale的中心holder: 例如, 在
 * MessageSourceAccessor中. DispatcherServlet会在此处自动公开其当前Locale
 * 设置. 其他应用程序也可以公开它们的类, 使类类似MessageSourceAccessor自动使用该
 * Locale设置.
 *
 * @author Juergen Hoeller
 * @author Nicholas Williams
 * @since 1.2
 * @see LocaleContext
 * @see org.springframework.context.support.MessageSourceAccessor
 * @see org.springframework.web.servlet.DispatcherServlet
 */
public final class LocaleContextHolder {

	private static final ThreadLocal<LocaleContext> localeContextHolder =
			new NamedThreadLocal<>("LocaleContext");

	private static final ThreadLocal<LocaleContext> inheritableLocaleContextHolder =
			new NamedInheritableThreadLocal<>("LocaleContext");

	// 框架级别的共享默认locale
	@Nullable
	private static Locale defaultLocale;

	// 框架级别的共享默认TimeZone
	@Nullable
	private static TimeZone defaultTimeZone;


	private LocaleContextHolder() {
	}


	/**
	 * 重置当前线程的LocaleContext
	 */
	public static void resetLocaleContext() {
		localeContextHolder.remove();
		inheritableLocaleContextHolder.remove();
	}

	/**
	 * 将给定的LocaleContext与当前线程关联, 而<i>不是</i>将其暴露为子线程的可继承对象.
	 * <p>给定的LocaleContext可能是{@link TimeZoneAwareLocaleContext}, 包含带有
	 * 相关时区信息的locale.
	 * @param localeContext 当前LocaleContext, 或为{@code null}以重置线程绑定context
	 * @see SimpleLocaleContext
	 * @see SimpleTimeZoneAwareLocaleContext
	 */
	public static void setLocaleContext(@Nullable LocaleContext localeContext) {
		setLocaleContext(localeContext, false);
	}

	/**
	 * 将给定的LocaleContext与当前线程关联.
	 * <p>给定的LocaleContext可能是一个{@link TimeZoneAwareLocaleContext},
	 * 包含带有相关时区信息的locale设置.
	 * @param localeContext 当前LocaleContext, 或为{@code null}以重置线程绑定context
	 * @param inheritable 是否将LocaleContext暴露为子线程可继承的(使用{@link InheritableThreadLocal})
	 * @see SimpleLocaleContext
	 * @see SimpleTimeZoneAwareLocaleContext
	 */
	public static void setLocaleContext(@Nullable LocaleContext localeContext, boolean inheritable) {
		if (localeContext == null) {
			resetLocaleContext();
		}
		else {
			if (inheritable) {
				inheritableLocaleContextHolder.set(localeContext);
				localeContextHolder.remove();
			}
			else {
				localeContextHolder.set(localeContext);
				inheritableLocaleContextHolder.remove();
			}
		}
	}

	/**
	 * 返回与当前线程关联的LocaleContext(如果有的话).
	 * @return 当前LocaleContext, 或者{@code null}(如果没有)
	 */
	@Nullable
	public static LocaleContext getLocaleContext() {
		LocaleContext localeContext = localeContextHolder.get();
		if (localeContext == null) {
			localeContext = inheritableLocaleContextHolder.get();
		}
		return localeContext;
	}

	/**
	 * 将给定的Locale与当前线程关联起来, 保留任何可能已经设置的TimeZone.
	 * <p>将隐式地为给定的Locale创建LocaleContext, 而<i>不是</i>将其暴露为子线程可继承的.
	 * @param locale 当前Locale, 或为{@code null}重置线程绑定context的locale设置部分
	 * @see #setTimeZone(TimeZone)
	 * @see SimpleLocaleContext#SimpleLocaleContext(Locale)
	 */
	public static void setLocale(@Nullable Locale locale) {
		setLocale(locale, false);
	}

	/**
	 * 将给定的Locale与当前线程关联起来, 暴力流任何可能已经设置的TimeZone.
	 * <p>将隐式地为给定的Locale创建一个LocaleContext.
	 * @param locale 当前locale设置, 或为{@code null}重置线程绑定context的locale部分
	 * @param inheritable 是否将LocaleContext暴露为子线程可继承的(使用{@link InheritableThreadLocal})
	 * @see #setTimeZone(TimeZone, boolean)
	 * @see SimpleLocaleContext#SimpleLocaleContext(Locale)
	 */
	public static void setLocale(@Nullable Locale locale, boolean inheritable) {
		LocaleContext localeContext = getLocaleContext();
		TimeZone timeZone = (localeContext instanceof TimeZoneAwareLocaleContext ?
				((TimeZoneAwareLocaleContext) localeContext).getTimeZone() : null);
		if (timeZone != null) {
			localeContext = new SimpleTimeZoneAwareLocaleContext(locale, timeZone);
		}
		else if (locale != null) {
			localeContext = new SimpleLocaleContext(locale);
		}
		else {
			localeContext = null;
		}
		setLocaleContext(localeContext, inheritable);
	}

	/**
	 * 在框架级别设置一个共享的默认locale, 作为JVM范围内的默认locale的替代方案.
	 * <p><b>注意:</b> 这对于设置与JVM范围内的默认locale不同的应用程序级默认locale非常有用.
	 * 然而, 这需要每个这样的应用程序对本地部署的Spring Framework jar进行操作.
	 * 在这种情况下, 不要将Spring部署为服务器级的共享库.
	 * @param locale 默认locale(如果没有, 则使用{@code null}, 让查找返回到{@link Locale#getDefault()})
	 * @since 4.3.5
	 * @see #getLocale()
	 * @see Locale#getDefault()
	 */
	public static void setDefaultLocale(@Nullable Locale locale) {
		LocaleContextHolder.defaultLocale = locale;
	}

	/**
	 * 返回与当前线程关联的Locale(如果有的话), 否则返回系统默认的Locale.
	 * 这实际上是{@link java.util.Locale#getDefault()}的替代品, 能够选择性地
	 * 尊重用户级别的Locale设置.
	 * <p>注意: 该方法可以在框架级别或JVM范围的系统级别退回到共享的默认Locale.
	 * 如果你想检查原始的LocaleContext内容(可能通过{@code null}指示没有特定的locale设置),
	 * 请使用{@link #getLocaleContext()}并调用{@link LocaleContext#getLocale()}
	 * @return 当前Locale, 如果没有特定的Locale与当前线程关联, 则为系统默认的Locale
	 * @see #getLocaleContext()
	 * @see LocaleContext#getLocale()
	 * @see #setDefaultLocale(Locale)
	 * @see java.util.Locale#getDefault()
	 */
	public static Locale getLocale() {
		return getLocale(getLocaleContext());
	}

	/**
	 * 返回与给定用户context关联Locale(如果有的话), 否则返回系统默认的Locale.
	 * 这实际上是{@link java.util.Locale#getDefault()}的替代品, 能够选择性地
	 * 尊重用户级别的Locale设置.
	 * @param localeContext 要检查的用户级locale
	 * @return 当前Locale, 如果没有特定的Locale与当前线程关联, 则为系统默认的Locale
	 * @since 5.0
	 * @see #getLocale()
	 * @see LocaleContext#getLocale()
	 * @see #setDefaultLocale(Locale)
	 * @see java.util.Locale#getDefault()
	 */
	public static Locale getLocale(@Nullable LocaleContext localeContext) {
		if (localeContext != null) {
			Locale locale = localeContext.getLocale();
			if (locale != null) {
				return locale;
			}
		}
		return (defaultLocale != null ? defaultLocale : Locale.getDefault());
	}

	/**
	 * 将给定的TimeZone与当前线程关联, 保留任何可能已经设置的Locale.
	 * <p>将隐式地为给定的Locale创建LocaleContext, 而<i>不是</i>将其暴露为子线程可继承的.
	 * @param timeZone 当前TimeZone, 或为{@code null}重置线程绑定context的时区部分
	 * @see #setLocale(Locale)
	 * @see SimpleTimeZoneAwareLocaleContext#SimpleTimeZoneAwareLocaleContext(Locale, TimeZone)
	 */
	public static void setTimeZone(@Nullable TimeZone timeZone) {
		setTimeZone(timeZone, false);
	}

	/**
	 * 将给定的TimeZone与当前线程关联, 保留任何可能已经设置的Locale设置.
	 * <p>将隐式地为给定的Locale创建一个LocaleContext.
	 * @param timeZone 当前TimeZone, 或为{@code null}重置线程绑定context的时区部分
	 * @param inheritable 是否将LocaleContext暴露为子线程可继承的(使用{@link InheritableThreadLocal})
	 * @see #setLocale(Locale, boolean)
	 * @see SimpleTimeZoneAwareLocaleContext#SimpleTimeZoneAwareLocaleContext(Locale, TimeZone)
	 */
	public static void setTimeZone(@Nullable TimeZone timeZone, boolean inheritable) {
		LocaleContext localeContext = getLocaleContext();
		Locale locale = (localeContext != null ? localeContext.getLocale() : null);
		if (timeZone != null) {
			localeContext = new SimpleTimeZoneAwareLocaleContext(locale, timeZone);
		}
		else if (locale != null) {
			localeContext = new SimpleLocaleContext(locale);
		}
		else {
			localeContext = null;
		}
		setLocaleContext(localeContext, inheritable);
	}

	/**
	 * 在框架级别设置一个共享的默认时区, 作为JVM范围内默认时区的替代方案.
	 * <p><b>注意:</b> 这对于设置与JVM范围内的默认时区不同的应用程序级别的默认时区非常有用.
	 * 然而, 这需要每个这样的应用程序对本地部署的Spring Framework jar进行操作.
	 * 在这种情况下, 不要将Spring部署为服务器级别的共享库!
	 * @param timeZone 默认时区(如果没有, 则为{@code null}, 让查找返回到{@link TimeZone#getDefault()})
	 * @since 4.3.5
	 * @see #getTimeZone()
	 * @see TimeZone#getDefault()
	 */
	public static void setDefaultTimeZone(@Nullable TimeZone timeZone) {
		defaultTimeZone = timeZone;
	}

	/**
	 * 返回与当前线程关联的TimeZone(如果有的话), 否则返回系统默认的TimeZone.
	 * 这实际上是{@link java.util.TimeZone#getDefault()}的替代品, 能够选择性地
	 * 尊重用户级别的TimeZone设置.
	 * <p>注意: 该方法可以在框架级别或JVM范围的系统级别退回到共享默认TimeZone.
	 * 如果你想检查原始LocaleContext内容(可能通过{@code null}指定没有特定的时区),
	 * 请使用{@link #getLocaleContext()}并在向下转换为{@link TimeZoneAwareLocaleContext}
	 * 后调用{@link TimeZoneAwareLocaleContext#getTimeZone()}.
	 * @return 当前的TimeZone, 如果没有特定的TimeZone与当前线程关联, 则为系统默认的TimeZone
	 * @see #getLocaleContext()
	 * @see TimeZoneAwareLocaleContext#getTimeZone()
	 * @see #setDefaultTimeZone(TimeZone)
	 * @see java.util.TimeZone#getDefault()
	 */
	public static TimeZone getTimeZone() {
		return getTimeZone(getLocaleContext());
	}

	/**
	 * 返回与给定用户context关联的TimeZone(如果有的话), 否则返回系统默认的TimeZone.
	 * 这实际上是{@link java.util.TimeZone#getDefault()}的替代品, 呢能够选择性地
	 * 尊重用户级别的TimeZone设置.
	 * @param localeContext 要检查的用户级locale
	 * @return 当前的TimeZone, 如果没有特定的TimeZone与当前线程关联, 则为系统默认的TimeZone
	 * @since 5.0
	 * @see #getTimeZone()
	 * @see TimeZoneAwareLocaleContext#getTimeZone()
	 * @see #setDefaultTimeZone(TimeZone)
	 * @see java.util.TimeZone#getDefault()
	 */
	public static TimeZone getTimeZone(@Nullable LocaleContext localeContext) {
		if (localeContext instanceof TimeZoneAwareLocaleContext) {
			TimeZone timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone();
			if (timeZone != null) {
				return timeZone;
			}
		}
		return (defaultTimeZone != null ? defaultTimeZone : TimeZone.getDefault());
	}

}
